package terra.model;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;

import terra.database.DatabaseFactory;
import terra.managers.MapManager;
import terra.managers.ThreadPoolManager;
import terra.model.object.GameObject;
import terra.model.object.Player;
import terra.model.object.interfaces.Updatable;
import terra.model.object.map.MapObject;
import terra.network.Connections;
import terra.network.packet.server.ObjectDelete;

public class World implements Runnable {
	/**
	 * 
	 */
	public GroundListener groundListener;
	private static World instance;
	private ConcurrentHashMap<Integer, GameObject> objects = new ConcurrentHashMap<Integer, GameObject>();
	private ConcurrentHashMap<Integer, GameObject> updateObjects = new ConcurrentHashMap<Integer, GameObject>(
			1000);
	public Thread thread;
	public static ConcurrentHashMap<MapObject, ScheduledFuture<?>> nulledWater = new ConcurrentHashMap<MapObject, ScheduledFuture<?>>();
	public static int id = 1;

	public World(){
		groundListener = new GroundListener();
		try {
			id = extractUsedObjectIDTable()+1;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("Next available OBJECTID " + id);
	}
	
	private static final String[][] ID_EXTRACTS = {
		{"characters","objectId"},
		{"items","objectId"},
		{"npcs","objectId"},
		{"staticobjects","objectId"}
	};
	
	public void init() {
		thread = new Thread(this);
		thread.setName("world update thread");
		thread.setPriority(Thread.NORM_PRIORITY + 2);
		thread.start();
	}

	public static World getInstance() {
		if (instance == null){
			instance = new World();
		}
		return instance;
	}

	public ConcurrentHashMap<Integer, GameObject> getObjects() {
		return objects;
	}

	public void setObjects(ConcurrentHashMap<Integer, GameObject> objects) {
		this.objects = objects;
	}

	public void addObject(int objectId, GameObject object) {
		getObjects().putIfAbsent(objectId, object);
		MapManager.getInstance().getMap(object.getMapIndex()).addObject(object);
		addUpdateObject(object);
	}

	public void removeObject(GameObject object) {
		if (object == null)
			return;
		object.setRemove(true);
		getUpdateObjects().remove(object.getObjectId());
		MapManager.getInstance().getMap(object.getMapIndex())
				.removeObject(object);
		getObjects().remove(object.getObjectId());
		if (Connections.connections.size() != 0)
			Connections.broadcast(new ObjectDelete(object.getObjectId()));
	}

	public void removeObject(int objectId) {
		GameObject object = getObjects().get(objectId);
		if (object == null)
			return;
		removeObject(object);
	}

	public ConcurrentHashMap<Integer, GameObject> getUpdateObjects() {
		return updateObjects;
	}

	public void addUpdateObject(GameObject object) {
		if (object == null)
			return;
		if (object instanceof Player
				|| (!object.isRemove() && object.getMap() != null && object
						.getMap().active))
			updateObjects.putIfAbsent(object.getObjectId(), object);
	}

	@Override
	public void run() {
		long time;
		int sleep;
		while (true) {
			time = System.currentTimeMillis();
			for (Updatable u : getUpdateObjects().values()) {
				getUpdateObjects().remove(((GameObject) u).getObjectId());
				u.update(1000 / 60);
			}
			try {
				sleep = (int) (System.currentTimeMillis() - time);
				sleep = 1000 / 60 - sleep;
				if (sleep < 0)
					sleep = 1;
				Thread.sleep(sleep);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public void addNulledWater(MapObject mapObject) {
		if (nulledWater.containsKey(mapObject))
			return;
		mapObject.sendTileInfo();

		ScheduledFuture<?> future = ThreadPoolManager.getInstance()
				.scheduleGeneral(new NulledWaterExecutor(mapObject), 5000);
		nulledWater.put(mapObject, future);
	}

	public static synchronized void objectChangeMap(GameObject object,
			int oldMapIndex) {
		MapManager.getInstance().getMaps().get(oldMapIndex)
				.removeObject(object);
		MapManager.getInstance().getMaps().get(object.getMapIndex())
				.addObject(object);
		if (!MapManager.getInstance().getMaps().get(object.getMapIndex()).active)
			World.getInstance().getUpdateObjects().remove(object);
		// if (object.getMapIndex() != MapManager.getInstance().minMapIndex &&
		// object.getMapIndex() != MapManager.getInstance().maxMapIndex) {
		// World.getInstance().getUpdateObjects().remove(object);
		// }
	}

	public static synchronized int getNextId() {
		return id++;
	}

	class NulledWaterExecutor implements Runnable {

		MapObject obj;

		NulledWaterExecutor(MapObject o) {
			obj = o;
		}

		@Override
		public void run() {
			nulledWater.remove(obj);
		}

	}
	
	/**
	 * @return
	 * @throws Exception
	 * @throws SQLException
	 */
	protected final int extractUsedObjectIDTable() throws Exception
	{
		try (Connection con = DatabaseFactory.getInstance().getConnection();
			Statement s = con.createStatement())
		{
			final ArrayList<Integer> temp = new ArrayList<Integer>();
			
			String ensureCapacityQuery = "SELECT ";
			String extractUsedObjectIdsQuery = "";
			
			for (String[] tblClmn : ID_EXTRACTS)
			{
				ensureCapacityQuery += "(SELECT COUNT(*) FROM " + tblClmn[0] + ") + ";
				extractUsedObjectIdsQuery += "SELECT " + tblClmn[1] + " FROM " + tblClmn[0] + " UNION ";
			}
			ensureCapacityQuery = ensureCapacityQuery.substring(0, ensureCapacityQuery.length() - 3); // Remove the last " + "
			extractUsedObjectIdsQuery = extractUsedObjectIdsQuery.substring(0, extractUsedObjectIdsQuery.length() - 7); // Remove the last " UNION "
			
//			try (ResultSet rs = s.executeQuery(ensureCapacityQuery))
//			{
//				rs.next();
//				temp.ensureCapacity(rs.getInt(1));
//			}
			
			try (ResultSet rs = s.executeQuery(extractUsedObjectIdsQuery))
			{
				while (rs.next())
				{
					temp.add(rs.getInt(1));
				}
			}

			int max = 0;
			for(int i : temp){
				if (i>max)
					max = i;
			}
			return max;
		}
	}

}
